Skip to content

CM-62984: Add Codex CLI support to ai-guardrails#436

Open
MaorDavidzon wants to merge 4 commits intomainfrom
CM-62984-add-codex-cli-support
Open

CM-62984: Add Codex CLI support to ai-guardrails#436
MaorDavidzon wants to merge 4 commits intomainfrom
CM-62984-add-codex-cli-support

Conversation

@MaorDavidzon
Copy link
Copy Markdown
Contributor

@MaorDavidzon MaorDavidzon commented Apr 20, 2026

Summary

  • Extend cycode ai-guardrails to support OpenAI Codex CLI alongside Cursor and Claude Code: installs ~/.codex/hooks.json for UserPromptSubmit, SessionStart, and PreToolUse:Bash; auto-enables [features] codex_hooks = true in ~/.codex/config.toml while preserving existing keys.
  • New canonical CommandExec event + handle_before_command_exec handler to scan shell commands the agent is about to run for secrets — closest Codex equivalent to the FileRead / McpExecution events (Codex's PreToolUse currently only intercepts Bash).
  • New module codex_config.py safely merges the feature flag via tomllib / tomli + tomli-w; CodexResponseBuilder reuses Claude Code response shapes verbatim (Codex accepts them).

Scope note

Codex hooks intercept UserPromptSubmit and PreToolUse:Bash only — not MCP calls or file reads. So FileRead and McpExecution canonical events cannot be wired for Codex today. This MR ships what's possible; coverage can expand as Codex extends its hook surface.

New direct deps

  • tomli-w (py3.9+) — TOML writer (stdlib has only a reader)
  • tomli (py<3.11 only) — backport of stdlib tomllib

Test plan

  • poetry run pytest tests/cli/commands/ai_guardrails/ -v — 150 passing (23 new)
  • poetry run pytest tests/ — 713 passing
  • ruff check + ruff format clean on all touched files
  • Cross-IDE isolation: a Cursor-shape payload piped into scan --ide codex is skipped ({} returned)
  • Manual E2E: install against Codex 0.45 on macOS, run codex exec — hook fires and Codex honors the response
  • CI green on this branch

🤖 Generated with Claude Code

Extend ai-guardrails hooks to cover OpenAI Codex CLI alongside Cursor and
Claude Code. Installs ~/.codex/hooks.json for UserPromptSubmit, SessionStart,
and PreToolUse:Bash events, and merges `[features] codex_hooks = true` into
~/.codex/config.toml while preserving existing keys. Adds a new canonical
CommandExec event for Bash command scanning since Codex's PreToolUse only
intercepts Bash today. CodexResponseBuilder reuses the Claude Code response
shapes (Codex accepts them verbatim). Adds tomli-w (and tomli on py<3.11)
as direct deps to manage the Codex TOML config safely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MaorDavidzon and others added 3 commits April 20, 2026 15:31
Post-merge fixes after #434 (session-start refactor) landed on main:

- consts.py: Codex SessionStart now calls CYCODE_SESSION_START_COMMAND
  with --ide codex, matching the Cursor/Claude-code pattern (the old
  CYCODE_ENSURE_AUTH_COMMAND constant was removed).
- session_start_command.py: add Codex branch to _build_session_payload
  so session_start works for the Codex IDE.
- handlers.py: drop ai_client.create_conversation() call from
  handle_before_command_exec; conversation creation now happens in the
  session-start command, matching the other three handlers.
- test_hooks_manager.py: update Codex SessionStart test to assert on
  CYCODE_SESSION_START_COMMAND + --ide codex.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
)


def handle_before_command_exec(ctx: typer.Context, payload: AIHookPayload, policy: dict) -> dict:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty similar to other handlers like mcp, if we could reduce duplicate code it would be nice

mcp_server_name: Optional[str] = None # For mcp_execution events
mcp_tool_name: Optional[str] = None # For mcp_execution events
mcp_arguments: Optional[dict] = None # For mcp_execution events
command: Optional[str] = None # For command_exec events (e.g., Codex PreToolUse:Bash)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not specific to codex

Comment on lines +253 to +255
else:
# Unknown or unsupported event combination; fall back to raw event name
canonical_event = CODEX_EVENT_MAPPING.get(hook_event_name, hook_event_name)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about read file / mcp?

Comment on lines 189 to 190
elif hook_event_name == 'PreToolUse':
canonical_event = AiHookEventType.FILE_READ if tool_name == 'Read' else AiHookEventType.MCP_EXECUTION
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add bash here?



# IDE-specific event name mappings to canonical types
CURSOR_EVENT_MAPPING = {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does cursor has dedicated bash event?

@@ -77,6 +87,13 @@ def _get_claude_code_hooks_dir() -> Path:
hooks_file_name='settings.json',
hook_events=['UserPromptSubmit', 'PreToolUse:Read', 'PreToolUse:mcp'],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add bash?

}


def _get_codex_hooks_config(async_mode: bool = False) -> dict:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks pretty much the same as claude, can we reduce code duplication here

if ide == AIIDEType.CODEX:
return AIHookPayload(
conversation_id=payload.get('session_id'),
generation_id=payload.get('turn_id'),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have user email?

mcp_servers, enabled_plugins = _get_claude_code_session_context()
elif ide == AIIDEType.CURSOR:
mcp_servers, enabled_plugins = _get_cursor_session_context()
else:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have mcps / plugins in codex?



@pytest.fixture
def codex_policy() -> dict[str, Any]:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its not really specific to codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants